Issues (52)

src/Command/LoggingService.php (2 issues)

1
<?php
2
3
namespace Lagdo\DbAdmin\Command;
4
5
use Lagdo\DbAdmin\Config\AuthInterface;
6
use Lagdo\Facades\Logger;
7
use Lagdo\DbAdmin\Db\DbFacade;
8
use Lagdo\DbAdmin\Driver\Db\ConnectionInterface;
9
use Lagdo\DbAdmin\Driver\DriverInterface;
10
11
use function gmdate;
12
use function json_encode;
13
14
/**
15
 * SQL queries logging and storage.
16
 */
17
class LoggingService
18
{
19
    /**
20
     * @var int
21
     */
22
    private const CAT_LIBRARY = 1;
23
24
    /**
25
     * @var int
26
     */
27
    private const CAT_ENDUSER = 2;
28
29
    /**
30
     * @var int
31
     */
32
    private const CAT_HISTORY = 3;
33
34
    /**
35
     * @var bool
36
     */
37
    private bool $enduserEnabled;
38
39
    /**
40
     * @var bool
41
     */
42
    private bool $historyEnabled;
43
44
    /**
45
     * @var bool
46
     */
47
    private bool $historyDistinct;
48
49
    /**
50
     * @var int
51
     */
52
    private int $historyLimit;
53
54
    /**
55
     * @var int
56
     */
57
    private int $category;
58
59
    /**
60
     * @var int|null
61
     */
62
    private int|null $ownerId = null;
63
64
    /**
65
     * @var ConnectionInterface
66
     */
67
    private ConnectionInterface $connection;
68
69
    /**
70
     * The constructor
71
     *
72
     * @param AuthInterface $auth
73
     * @param DbFacade $db
74
     * @param DriverInterface $driver
75
     * @param array $database
76
     * @param array $options
77
     */
78
    public function __construct(private AuthInterface $auth, private DbFacade $db,
79
        DriverInterface $driver, array $database, array $options)
80
    {
81
        $this->connection = $driver->createConnection($database);
82
        $this->connection->open($database['name'], $database['schema'] ?? '');
83
        $this->enduserEnabled = (bool)($options['enduser']['enabled'] ?? false);
84
        $this->historyEnabled = (bool)($options['history']['enabled'] ?? false);
85
        $this->historyDistinct = (bool)($options['history']['distinct'] ?? false);
86
        $this->historyLimit = (int)($options['history']['limit'] ?? 15);
87
        $this->category = self::CAT_ENDUSER;
88
    }
89
90
    /**
91
     * @return void
92
     */
93
    public function setCategoryToHistory(): void
94
    {
95
        $this->category = self::CAT_HISTORY;
96
    }
97
98
    /**
99
     * @param string $username
100
     *
101
     * @return int
102
     */
103
    private function readOwnerId(string $username): int
104
    {
105
        $statement = "select id from dbadmin_owners where username='$username' limit 1";
106
        $ownerId = $this->connection->result($statement);
107
        return $this->ownerId = !$ownerId ? 0 : (int)$ownerId;
108
    }
109
110
    /**
111
     * @param string $username
112
     *
113
     * @return int
114
     */
115
    private function newOwnerId(string $username): int
116
    {
117
        // Try to save the user and return his id.
118
        $statement = $this->connection
119
            ->query("insert into dbadmin_owners(username) values('$username')");
0 ignored issues
show
The method query() does not exist on Lagdo\DbAdmin\Driver\Db\ConnectionInterface. It seems like you code against a sub-type of Lagdo\DbAdmin\Driver\Db\ConnectionInterface such as Lagdo\DbAdmin\Driver\Db\Connection. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

119
        /** @scrutinizer ignore-call */ 
120
        $statement = $this->connection
Loading history...
120
        if ($statement !== false) {
121
            return $this->readOwnerId($username);
122
        }
123
124
        Logger::warning('Unable to save new owner in the query logging database.', [
125
            'error' => $this->connection->error(),
126
        ]);
127
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the type-hinted return integer.
Loading history...
128
    }
129
130
    /**
131
     * @return int
132
     */
133
    private function getOwnerId(): int
134
    {
135
        return $this->ownerId !== null ? $this->ownerId :
136
            ($this->readOwnerId($this->auth->user()) ?:
137
                $this->newOwnerId($this->auth->user()));
138
    }
139
140
    /**
141
     * @return bool
142
     */
143
    private function enduserDisabled(): bool
144
    {
145
        return (!$this->enduserEnabled && !$this->historyEnabled) ||
146
            !$this->auth->user() || !$this->getOwnerId();
147
    }
148
149
    /**
150
     * @param int $category
151
     *
152
     * @return bool
153
     */
154
    private function categoryDisabled(int $category): bool
155
    {
156
        return (!$this->enduserEnabled && $category === self::CAT_ENDUSER) ||
157
            ($category < self::CAT_ENDUSER || $category > self::CAT_HISTORY);
158
    }
159
160
    /**
161
     * @param string $query
162
     * @param int $category
163
     *
164
     * @return bool
165
     */
166
    private function saveRunnedCommand(string $query, int $category): bool
167
    {
168
        if ($this->categoryDisabled($category)) {
169
            return false;
170
        }
171
172
        $options = $this->db->getDatabaseOptions();
173
        if (isset($options['password'])) {
174
            $options['password'] = '';
175
        }
176
        $driver = $options['driver'];
177
        $options = json_encode($options) ?? '{}';
178
        // Duplicates on query are checked on client side, not here.
179
        $ownerId = $this->getOwnerId();
180
        $now = gmdate('Y-m-d H:i:s');
181
        $statement = "insert into dbadmin_runned_commands" .
182
            "(query,driver,options,category,last_update,owner_id) " .
183
            "values('$query','$driver','$options',$category,'$now',$ownerId)";
184
        $statement = $this->connection->query($statement) !== false;
185
        if ($statement !== false) {
186
            return true;
187
        }
188
189
        Logger::warning('Unable to save command in the query logging database.', [
190
            'error' => $this->connection->error(),
191
        ]);
192
        return false;
193
    }
194
195
    /**
196
     * @param string $query
197
     *
198
     * @return bool
199
     */
200
    public function saveCommand(string $query): bool
201
    {
202
        $category = $this->category;
203
        // Reset to the default category.
204
        $this->category = self::CAT_ENDUSER;
205
        return $this->enduserDisabled() ? false :
206
            $this->saveRunnedCommand($query, $category);
207
    }
208
209
    /**
210
     * @param int $category
211
     *
212
     * @return array
213
     */
214
    private function getCommands(int $category): array
215
    {
216
        if ($this->enduserDisabled()) {
217
            return [];
218
        }
219
220
        $ownerId = $this->getOwnerId();
221
        $statement = $this->historyDistinct ?
222
            "select max(id) as id,query from dbadmin_runned_commands c " .
223
                "where c.owner_id=$ownerId and c.category=$category " .
224
                "group by query order by c.last_update desc limit {$this->historyLimit}" :
225
            "select id,query from dbadmin_runned_commands c " .
226
                "where c.owner_id=$ownerId and c.category=$category " .
227
                "order by c.last_update desc limit {$this->historyLimit}";
228
        $statement = $this->connection->query($statement);
229
        if ($statement !== false) {
230
            $commands = [];
231
            while (($row = $statement->fetchAssoc())) {
232
                $commands[$row['id']] = $row['query'];
233
            }
234
            return $commands;
235
        }
236
237
        Logger::warning('Unable to read commands from the query logging database.', [
238
            'error' => $this->connection->error(),
239
        ]);
240
        return [];
241
    }
242
243
    /**
244
     * @return array
245
     */
246
    public function getHistoryCommands(): array
247
    {
248
        return !$this->historyEnabled ? [] : $this->getCommands(self::CAT_HISTORY);
249
    }
250
}
251