Passed
Pull Request — master (#4676)
by Nils
05:55
created

subtasksHandler()   C

Complexity

Conditions 14
Paths 34

Size

Total Lines 104
Code Lines 51

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 14
eloc 51
c 1
b 0
f 0
nc 34
nop 3
dl 0
loc 104
rs 6.2666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Teampass - a collaborative passwords manager.
4
 * ---
5
 * This file is part of the TeamPass project.
6
 * 
7
 * TeamPass is free software: you can redistribute it and/or modify it
8
 * under the terms of the GNU General Public License as published by
9
 * the Free Software Foundation, version 3 of the License.
10
 * 
11
 * TeamPass is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
 * GNU General Public License for more details.
15
 * 
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18
 * 
19
 * Certain components of this file may be under different licenses. For
20
 * details, see the `licenses` directory or individual file headers.
21
 * ---
22
 * @file      background_tasks___userKeysCreation.php
23
 * @author    Nils Laumaillé ([email protected])
24
 * @copyright 2009-2025 Teampass.net
25
 * @license   GPL-3.0
26
 * @see       https://www.teampass.net
27
 */
28
29
use TeampassClasses\NestedTree\NestedTree;
30
use TeampassClasses\SessionManager\SessionManager;
31
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
32
use Symfony\Component\Process\Process;
33
use Symfony\Component\Process\PhpExecutableFinder;
34
use TeampassClasses\Language\Language;
35
use TeampassClasses\ConfigManager\ConfigManager;
36
37
// Load functions
38
require_once __DIR__.'/../sources/main.functions.php';
39
40
// init
41
loadClasses('DB');
42
$session = SessionManager::getSession();
43
$request = SymfonyRequest::createFromGlobals();
44
$lang = new Language('english');
45
46
// Load config
47
$configManager = new ConfigManager();
48
$SETTINGS = $configManager->getAllSettings();
49
50
// Define Timezone
51
date_default_timezone_set($SETTINGS['timezone'] ?? 'UTC');
52
53
// Set header properties
54
header('Content-type: text/html; charset=utf-8');
55
header('Cache-Control: no-cache, no-store, must-revalidate');
56
error_reporting(E_ERROR);
57
// increase the maximum amount of time a script is allowed to run
58
set_time_limit($SETTINGS['task_maximum_run_time']);
59
60
// --------------------------------- //
61
62
require_once __DIR__.'/background_tasks___functions.php';
63
64
$tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
65
66
// Get PHP binary
67
$phpBinaryPath = getPHPBinary();
68
69
$processToPerform = DB::queryfirstrow(
70
    'SELECT *
71
    FROM ' . prefixTable('background_tasks') . '
72
    WHERE (finished_at IS NULL OR finished_at = "") AND process_type = %s
73
    ORDER BY increment_id ASC',
74
    'create_user_keys'
75
);
76
77
// Check if there is a task to execute
78
if (DB::count() > 0) {
79
    // Execute or continue the task
80
    subtasksHandler($processToPerform['increment_id'], $processToPerform['arguments'], $phpBinaryPath);
81
}
82
83
/**
84
 * Function to handle the subtasks
85
 * 
86
 * @param int $taskId Task identifier
87
 * @param string $taskArguments Task arguments
88
 * @param string $phpBinaryPath Path to the PHP binary
89
 * 
90
 * @return void
91
 */
92
function subtasksHandler($taskId, $taskArguments, $phpBinaryPath): void
93
{
94
    // Check if subtasks are still running
95
    // This in order to prevent the script from running multiple times on same objects
96
    while (DB::queryFirstField(
97
        'SELECT COUNT(*) FROM ' . prefixTable('background_subtasks') . ' 
98
        WHERE is_in_progress = 1'
99
    ) > 0) {
100
        sleep(10); // Wait 10 seconds before continuing
101
    }
102
    // Are server processes still running?
103
    serverProcessesHandler();
104
105
    // Get list of subtasks to be performed for this task id
106
    $subTasks = getSubTasks($taskId);
107
    $taskArgumentsArray = json_decode($taskArguments, true);
108
109
    if (count($subTasks) === 0) {
110
        // No subtasks to perform
111
        // Task is finished
112
        markTaskAsFinished($taskId, $taskArgumentsArray['new_user_id']);
113
114
        // Clear all subtasks
115
        DB::delete(prefixTable('background_subtasks'), 'task_id=%i', $taskId);        
116
117
        // Exit
118
        return;
119
    }
120
    
121
    foreach ($subTasks as $subTask) {
122
        // Launch the process for this subtask
123
124
        // Do we have already 5 process running?
125
        if (countActiveSymfonyProcesses() <= 2) {
126
            // Extract the subtask parameters
127
            $subTaskParams = json_decode($subTask['task'], true);
128
129
            if (WIP === true) {
130
                error_log('Subtask in progress: '.$subTask['increment_id']." (".$taskId.") - "./** @scrutinizer ignore-type */ print_r($subTaskParams,true));
131
            }
132
            
133
            // Build all subtasks if first one
134
            if ((int) $subTaskParams['index'] === 0) {
135
                if ($subTaskParams['step'] === 'step20') {
136
                    // Get total number of items
137
                    DB::query(
138
                        'SELECT *
139
                        FROM ' . prefixTable('items') . '
140
                        '.(isset($taskArgumentsArray['only_personal_items']) === true && $taskArgumentsArray['only_personal_items'] === 1 ? 'WHERE perso = 1' : '')
141
                    );
142
                    createAllSubTasks($subTaskParams['step'], DB::count(), $subTaskParams['nb'], $taskId);
143
144
                } elseif ($subTaskParams['step'] === 'step30') {
145
                    // Get total number of items
146
                    DB::query(
147
                        'SELECT *
148
                        FROM ' . prefixTable('log_items') . '
149
                        WHERE raison LIKE "at_pw :%" AND encryption_type = "teampass_aes"'
150
                    );
151
                    createAllSubTasks($subTaskParams['step'], DB::count(), $subTaskParams['nb'], $taskId);
152
153
                } elseif ($subTaskParams['step'] === 'step40') {
154
                    // Get total number of items
155
                    DB::query(
156
                        'SELECT *
157
                        FROM ' . prefixTable('categories_items') . '
158
                        WHERE encryption_type = "teampass_aes"'
159
                    );
160
                    createAllSubTasks($subTaskParams['step'], DB::count(), $subTaskParams['nb'], $taskId);
161
162
                } elseif ($subTaskParams['step'] === 'step50') {
163
                    // Get total number of items
164
                    DB::query(
165
                        'SELECT *
166
                        FROM ' . prefixTable('suggestion')
167
                    );
168
                    createAllSubTasks($subTaskParams['step'], DB::count(), $subTaskParams['nb'], $taskId);
169
170
                } elseif ($subTaskParams['step'] === 'step60') {
171
                    // Get total number of items
172
                    DB::query(
173
                        'SELECT *
174
                        FROM ' . prefixTable('files') . ' AS f
175
                        INNER JOIN ' . prefixTable('items') . ' AS i ON i.id = f.id_item
176
                        WHERE f.status = "' . TP_ENCRYPTION_NAME . '"'
177
                    );
178
                    createAllSubTasks($subTaskParams['step'], DB::count(), $subTaskParams['nb'], $taskId);
179
                }
180
            }
181
            
182
            // Launch the subtask with the current parameters
183
            $process = new Process([$phpBinaryPath, __DIR__.'/background_tasks___userKeysCreation_subtaskHdl.php', $subTask['increment_id'], $subTaskParams['index'], $subTaskParams['nb'], $subTaskParams['step'], $taskArguments, $taskId]);
184
            $process->start();
185
            $pid = $process->getPid();
186
            
187
            // Update the subtask with the process id
188
            updateSubTask($subTask['increment_id'], ['pid' => $pid]);
189
            updateTask($taskId);
190
        }
191
    }
192
    sleep(10); // Wait 10 seconds before continuing
193
194
    // Recursively call this function until all subtasks are finished
195
    subtasksHandler($taskId, $taskArguments, $phpBinaryPath);
196
}
197
198
function createAllSubTasks($action, $totalElements, $elementsPerIteration, $taskId)
199
{
200
    // Check if subtasks have to be created
201
    DB::query(
202
        'SELECT *
203
        FROM ' . prefixTable('background_subtasks') . '
204
        WHERE task_id = %i AND task LIKE %ss',
205
        $taskId,
206
        $action
207
    );
208
209
    if (DB::count() > 1) {
210
        return;
211
    }
212
213
    $iterations = ceil($totalElements / $elementsPerIteration);
214
215
    for ($i = 1; $i < $iterations; $i++) {
216
        DB::insert(prefixTable('background_subtasks'), [
217
            'task_id' => $taskId,
218
            'created_at' => time(),
219
            'task' => json_encode([
220
                "step" => $action,
221
                "index" => $i * $elementsPerIteration,
222
                "nb" => $elementsPerIteration,
223
            ]),
224
        ]);
225
    }
226
}
227
228
function countActiveSymfonyProcesses() {
229
    // Count the number of active processes
230
    return DB::queryFirstField(
231
        'SELECT COUNT(*) FROM ' . prefixTable('background_subtasks') . 
232
        ' WHERE process_id IS NOT NULL AND finished_at IS NULL'
233
    );
234
}
235
236
/**
237
 * Function to get the subtasks of a task
238
 */
239
function getSubTasks($taskId) {
240
    $task_to_perform = DB::query(
241
        'SELECT *
242
        FROM ' . prefixTable('background_subtasks') . '
243
        WHERE task_id = %i AND finished_at IS NULL
244
        ORDER BY increment_id ASC',
245
        $taskId
246
    );
247
    return $task_to_perform;
248
}
249
250
251
/**
252
 * Update a subtask
253
 * 
254
 * @param int $subTaskId Subtask identifier
255
 * @param array $taskParams Task parameters to update
256
 */
257
function updateSubTask($subTaskId, $args) {
258
    if (empty($subTaskId) === true) {
259
        if (defined('LOG_TO_SERVER') && LOG_TO_SERVER === true) {
260
            error_log('Subtask ID is empty ... are we lost!?!');
261
        }
262
        return;
263
    }
264
    // Convert task parameters to JSON
265
    $query = [
266
        'updated_at' => time(), // Update the last modification date
267
        'is_in_progress' => 1,
268
        'sub_task_in_progress' => 1,
269
    ];
270
    if (isset($args['task']) === true) {
271
        $query['task'] = $args['task'];
272
    }
273
    if (isset($args['pid']) === true) {
274
        $query['process_id'] = $args['pid'];
275
    }
276
277
    // Update the subtask in the database
278
    DB::update(prefixTable('background_subtasks'), $query, 'increment_id=%i', $subTaskId);
279
}
280
281
282
/**
283
 * Reload subtask information from the database
284
 * 
285
 * @param int $subTaskId Subtask identifier
286
 * @return array Updated subtask information
287
 */
288
function reloadSubTask($subTaskId) {
289
    // Retrieve subtask information from the database
290
    $subTask = DB::queryFirstRow(
291
        'SELECT * FROM ' . prefixTable('background_subtasks') . ' WHERE increment_id = %i', 
292
        $subTaskId
293
    );
294
295
    // Return subtask information
296
    return $subTask;
297
}
298
299
function updateTask($taskId) {
300
    // Update the task in the database
301
    DB::update(prefixTable('background_tasks'), [
302
        'updated_at' => time(), // Update the last modification date
303
        'is_in_progress' => 1, // Update the task status
304
    ], 'increment_id=%i', $taskId);
305
}
306
307
308
/**
309
 * Mark a main task as finished
310
 * 
311
 * @param int $taskId Task identifier
312
 */
313
function markTaskAsFinished($taskId, $userId = null) {
314
    // Update the task in the database
315
    DB::update(prefixTable('background_tasks'), [
316
        'finished_at' => time(),
317
        'arguments' => '{"new_user_id":'.$userId.'}',
318
        'is_in_progress' => -1,
319
    ], 'increment_id=%i', $taskId);
320
}
321
322
function markSubTaskAsFinished($subTaskId) {
323
    // Update the subtask in the database
324
    DB::update(prefixTable('background_subtasks'), [
325
        'finished_at' => time(),
326
        'is_in_progress' => -1,
327
        'sub_task_in_progress' => 0,
328
    ], 'increment_id=%i', $subTaskId);
329
}
330
331
function serverProcessesHandler()
332
{
333
    // Get all processes
334
    $subtasks = DB::query(
335
        'SELECT *
336
        FROM ' . prefixTable('background_subtasks') . '
337
        WHERE process_id IS NOT NULL AND finished_at IS NULL'
338
    );
339
340
    // Check if processes are still running
341
    foreach ($subtasks as $subtask) {
342
        $command = ['ps', '-p', $subtask['process_id']];
343
344
        // Create a new process to execute the command
345
        $process = new Process($command);
346
347
        // Execute the command
348
        $process->run();
349
350
        // Retrieve the process output
351
        $output = $process->getOutput();
352
353
        // Check if the process is still running by checking the exit code
354
        if (strpos($output, $subtask['process_id'].' ') === false) {
355
            markSubTaskAsFinished($subtask['increment_id']);
356
        }
357
    }
358
}