Passed
Push — main ( 0f30b5...54eb22 )
by Rafael
05:37
created

Config::getMigrations()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 12
c 0
b 0
f 0
dl 0
loc 22
rs 9.8666
cc 4
nc 4
nop 0
1
<?php
2
3
/* Copyright (C) 2024       Rafael San José         <[email protected]>
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 3 of the License, or
8
 * any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License
16
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17
 */
18
19
namespace Alxarafe\Base;
20
21
use Alxarafe\Lib\Routes;
22
use Alxarafe\Lib\Trans;
23
use Alxarafe\Tools\Debug;
24
use CoreModules\Admin\Model\Migration;
25
use DebugBar\DebugBarException;
26
use stdClass;
27
28
/**
29
 * Manage the configuration file
30
 */
31
abstract class Config
32
{
33
    /**
34
     * Configuration filename.
35
     */
36
    private const CONFIG_FILENAME = 'config.json';
37
38
    /**
39
     * Defines the configuration file structure
40
     */
41
    public const  CONFIG_STRUCTURE = [
42
        'main' => [
43
            'path', // Path to the public folder (usually htdocs)
44
            'url',
45
            'data', // Route to the private folder that stores the documents.
46
            'theme',
47
            'language',
48
        ],
49
        'db' => [
50
            'type',
51
            'host',
52
            'user',
53
            'pass',
54
            'name',
55
            'port',
56
            'prefix',
57
            'charset',
58
            'collation',
59
            'encryption', // Pending review: If true, some database fields are encrypted.
60
            'encrypt_type', // Pending review: Encryption type ('0' if none, '1' if DES and '2' if AES)
61
        ],
62
        'security' => [
63
            'debug',
64
            'unique_id', // Unique identifier of the installation.
65
            'https', // If true, the use of https is forced (recommended)
66
            'jwt_secret_key',
67
        ]
68
    ];
69
70
    /**
71
     * Contains configuration file information
72
     *
73
     * @var stdClass|null
74
     */
75
    private static ?stdClass $config = null;
76
77
    private static array $messages = [];
78
79
    /**
80
     * Returns an array with the messages accumulated since the last call.
81
     *
82
     * @return array
83
     */
84
    public static function getMessages()
85
    {
86
        $result = self::$messages;
87
        self::$messages = [];
88
        return $result;
89
    }
90
91
    /**
92
     * Gets the information defined in the configuration file.
93
     * To reload the configuration file, set $reload to true.
94
     *
95
     * @param bool $reload
96
     * @return stdClass|null
97
     */
98
    public static function getConfig(bool $reload = false): ?stdClass
99
    {
100
        if ($reload || !isset(self::$config)) {
101
            self::$config = self::loadConfig($reload);
102
        }
103
104
        return self::$config;
105
    }
106
107
    /**
108
     * Add the configuration parameters received in $data in the configuration file.
109
     *
110
     * @param stdClass $data
111
     * @return bool
112
     * @throws DebugBarException
113
     */
114
    public static function setConfig(stdClass $data): bool
115
    {
116
        /**
117
         * If the configuration file is empty, we add the parameters
118
         * that we can obtain at runtime (getDefaultMainFileInfo).
119
         */
120
        if (empty(self::$config)) {
121
            self::$config = new stdClass();
122
            self::$config->main = static::getDefaultMainFileInfo();
123
        }
124
125
        foreach (self::CONFIG_STRUCTURE as $section => $values) {
126
            foreach ($values as $key) {
127
                if (!isset($data->$section)) {
128
                    error_log($section . ' is not defined!');
129
                    continue;
130
                }
131
                if (!isset($data->$section->$key)) {
132
                    error_log($key . ' is not defined in ' . $section . '!');
133
                    continue;
134
                }
135
                if (!isset(self::$config->$section)) {
136
                    self::$config->$section = new stdClass();
137
                }
138
                self::$config->$section->$key = $data->$section->$key;
139
            }
140
        }
141
142
        /**
143
         * Save the configuration in the configuration file.
144
         */
145
        Trans::setLang(self::$config->main->language ?? Trans::FALLBACK_LANG);
146
        $ok = self::saveConfig();
147
        self::getConfig(true);
148
        Debug::initialize(true);
149
        return $ok;
150
    }
151
152
    /**
153
     * Those configuration parameters that we can obtain at run time,
154
     * or their default values, are obtained.
155
     *
156
     * @return stdClass
157
     */
158
    public static function getDefaultMainFileInfo(): stdClass
159
    {
160
        $result = new stdClass();
161
        $result->path = constant('BASE_PATH');
162
        $result->url = constant('BASE_URL');
163
        return $result;
164
    }
165
166
    /**
167
     * Updates the configuration file with the information it has in memory.
168
     *
169
     * @return bool
170
     */
171
    private static function saveConfig(): bool
172
    {
173
        if (empty(self::$config)) {
174
            return true;
175
        }
176
        return file_put_contents(self::getConfigFilename(), json_encode(self::$config, JSON_PRETTY_PRINT)) !== false;
177
    }
178
179
    /**
180
     * Returns the config.json complete path.
181
     *
182
     * @return string
183
     */
184
    private static function getConfigFilename(): string
185
    {
186
        return realpath(constant('BASE_PATH') . '/..') . DIRECTORY_SEPARATOR . self::CONFIG_FILENAME;
187
    }
188
189
    /**
190
     * Obtains an associative array with all migrations. The index is the name of the
191
     * migration and the name of the module separated by an @ sign, which ensures that
192
     * the migration is registered as executed; and the value is the relative path of
193
     * the file containing the migration.
194
     *
195
     * @return array
196
     */
197
    private static function getMigrations(): array
198
    {
199
        $result = [];
200
201
        $routes = Routes::getAllRoutes();
202
        if (empty($routes['Migrations'])) {
203
            return $result;
204
        }
205
206
        $migrations = $routes['Migrations'];
207
208
        foreach ($migrations as $module => $data) {
209
            foreach ($data as $filename => $migration) {
210
                $route_array = explode('|', $migration);
211
                $filepath = $route_array[1];
212
213
                $result[$filename . '@' . $module] = $filepath;
214
            }
215
        }
216
        ksort($result);
217
218
        return $result;
219
    }
220
221
    /**
222
     * Runs pending migrations in alphabetical order.
223
     * By default, migration names are preceded by the date in Japanese format,
224
     * which ensures chronological execution.
225
     *
226
     * @return void
227
     */
228
    public static function runMigrations(): void
229
    {
230
        $result = static::getMigrations();
231
232
        $batch = 1 + Migration::getLastBatch();
233
        foreach ($result as $filename => $filepath) {
234
            if (Migration::where(['migration' => $filename])->first()) {
235
                continue;
236
            }
237
238
            $migration = require_once $filepath;
239
            $migration->up();
240
            Migration::create([
241
                'migration' => $filename,
242
                'batch' => $batch,
243
            ]);
244
        }
245
    }
246
247
    public static function runSeeders()
248
    {
249
        $routes = Routes::getAllRoutes();
250
        if (empty($routes['Seeders'])) {
251
            return;
252
        }
253
254
        $seeders = $routes['Seeders'];
255
256
        $result = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
257
        foreach ($seeders as $data) {
258
            foreach ($data as $seeder) {
259
                $route_array = explode('|', $seeder);
260
                $classname = $route_array[0];
261
                new $classname();
262
            }
263
        }
264
    }
265
266
    /**
267
     * Adds a new message that will then be returned by getMessages.
268
     *
269
     * @param $message
270
     * @return void
271
     */
272
    private static function addNewMessage($message)
0 ignored issues
show
Unused Code introduced by
The method addNewMessage() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
273
    {
274
        self::$messages[] = $message;
275
    }
276
277
    /**
278
     * Returns a stdClass with the program configuration.
279
     * If the configuration file does not exist, is not accessible, or is not correct, returns null.
280
     * The configuration is loaded from the file only once and stored in a variable. You can set $reload
281
     * to true to force a reload of the configuration file.
282
     *
283
     * @param bool $reload
284
     * @return stdClass|null
285
     */
286
    private static function loadConfig(bool $reload = false): ?stdClass
287
    {
288
        if (!$reload && isset(self::$config)) {
289
            return self::$config;
290
        }
291
292
        $filename = self::getConfigFilename();
293
        if (!file_exists($filename)) {
294
            return null;
295
        }
296
297
        $config = file_get_contents($filename);
298
        if ($config === false) {
299
            return self::$config;
300
        }
301
302
        $result = json_decode($config);
303
        if (json_last_error() === JSON_ERROR_NONE) {
304
            self::$config = $result;
305
        }
306
307
        return $result;
308
    }
309
}
310