Issues (26)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Migrator.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace Arrilot\BitrixMigrations;
4
5
use Arrilot\BitrixIblockHelper\HLBlock;
6
use Arrilot\BitrixIblockHelper\IblockId;
7
use Arrilot\BitrixMigrations\Constructors\FieldConstructor;
8
use Arrilot\BitrixMigrations\Interfaces\DatabaseStorageInterface;
9
use Arrilot\BitrixMigrations\Interfaces\FileStorageInterface;
10
use Arrilot\BitrixMigrations\Interfaces\MigrationInterface;
11
use Arrilot\BitrixMigrations\Storages\BitrixDatabaseStorage;
12
use Arrilot\BitrixMigrations\Storages\FileStorage;
13
use Bitrix\Main\Application;
14
use Exception;
15
16
class Migrator
17
{
18
    /**
19
     * Migrator configuration array.
20
     *
21
     * @var array
22
     */
23
    protected $config;
24
25
    /**
26
     * Directory to store m.
27
     *
28
     * @var string
29
     */
30
    protected $dir;
31
32
    /**
33
     * Directory to store archive m.
34
     *
35
     * @var string
36
     */
37
    protected $dir_archive;
38
39
    /**
40
     * User transaction default.
41
     *
42
     * @var bool
43
     */
44
    protected $use_transaction;
45
46
    /**
47
     * Files interactions.
48
     *
49
     * @var FileStorageInterface
50
     */
51
    protected $files;
52
53
    /**
54
     * Interface that gives us access to the database.
55
     *
56
     * @var DatabaseStorageInterface
57
     */
58
    protected $database;
59
60
    /**
61
     * TemplatesCollection instance.
62
     *
63
     * @var TemplatesCollection
64
     */
65
    protected $templates;
66
67
    /**
68
     * Constructor.
69
     *
70
     * @param array                    $config
71
     * @param TemplatesCollection      $templates
72
     * @param DatabaseStorageInterface $database
73
     * @param FileStorageInterface     $files
74
     */
75
    public function __construct($config, TemplatesCollection $templates, DatabaseStorageInterface $database = null, FileStorageInterface $files = null)
76
    {
77
        $this->config = $config;
78
        $this->dir = $config['dir'];
79
        $this->dir_archive = isset($config['dir_archive']) ? $config['dir_archive'] : 'archive';
80
        $this->use_transaction = isset($config['use_transaction']) ? $config['use_transaction'] : false;
81
82
        if (isset($config['default_fields']) && is_array($config['default_fields'])) {
83
            foreach ($config['default_fields'] as $class => $default_fields) {
84
                FieldConstructor::$defaultFields[$class] = $default_fields;
85
            }
86
        }
87
88
        $this->templates = $templates;
89
        $this->database = $database ?: new BitrixDatabaseStorage($config['table']);
90
        $this->files = $files ?: new FileStorage();
91
    }
92
93
    /**
94
     * Create migration file.
95
     *
96
     * @param string $name         - migration name
97
     * @param string $templateName
98
     * @param array  $replace      - array of placeholders that should be replaced with a given values.
99
     * @param string  $subDir
100
     *
101
     * @return string
102
     */
103
    public function createMigration($name, $templateName, array $replace = [], $subDir = '')
104
    {
105
        $targetDir = $this->dir;
106
        $subDir = trim(str_replace('\\', '/', $subDir), '/');
107
        if ($subDir) {
108
            $targetDir .= '/' . $subDir;
109
        }
110
111
        $this->files->createDirIfItDoesNotExist($targetDir);
112
113
        $fileName = $this->constructFileName($name);
114
        $className = $this->getMigrationClassNameByFileName($fileName);
115
        $templateName = $this->templates->selectTemplate($templateName);
116
117
        $template = $this->files->getContent($this->templates->getTemplatePath($templateName));
118
        $template = $this->replacePlaceholdersInTemplate($template, array_merge($replace, ['className' => $className]));
119
120
        $this->files->putContent($targetDir.'/'.$fileName.'.php', $template);
121
122
        return $fileName;
123
    }
124
125
    /**
126
     * Run all migrations that were not run before.
127
     */
128
    public function runMigrations()
129
    {
130
        $migrations = $this->getMigrationsToRun();
131
        $ran = [];
132
133
        if (empty($migrations)) {
134
            return $ran;
135
        }
136
137
        foreach ($migrations as $migration) {
138
            $this->runMigration($migration);
139
            $ran[] = $migration;
140
        }
141
142
        return $ran;
143
    }
144
145
    /**
146
     * Run a given migration.
147
     *
148
     * @param string $file
149
     *
150
     * @throws Exception
151
     *
152
     * @return string
153
     */
154 View Code Duplication
    public function runMigration($file)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
155
    {
156
        $migration = $this->getMigrationObjectByFileName($file);
157
158
        $this->disableBitrixIblockHelperCache();
159
160
        $this->checkTransactionAndRun($migration, function () use ($migration, $file) {
161
            if ($migration->up() === false) {
162
                throw new Exception("Migration up from {$file}.php returned false");
163
            }
164
        });
165
166
        $this->logSuccessfulMigration($file);
167
    }
168
169
    /**
170
     * Log successful migration.
171
     *
172
     * @param string $migration
173
     *
174
     * @return void
175
     */
176
    public function logSuccessfulMigration($migration)
177
    {
178
        $this->database->logSuccessfulMigration($migration);
179
    }
180
181
    /**
182
     * Get ran migrations.
183
     *
184
     * @return array
185
     */
186
    public function getRanMigrations()
187
    {
188
        return $this->database->getRanMigrations();
189
    }
190
191
    /**
192
     * Get all migrations.
193
     *
194
     * @return array
195
     */
196
    public function getAllMigrations()
197
    {
198
        return $this->files->getMigrationFiles($this->dir);
199
    }
200
201
    /**
202
     * Determine whether migration file for migration exists.
203
     *
204
     * @param string $migration
205
     *
206
     * @return bool
207
     */
208
    public function doesMigrationFileExist($migration)
209
    {
210
        return $this->files->exists($this->getMigrationFilePath($migration));
211
    }
212
213
    /**
214
     * Rollback a given migration.
215
     *
216
     * @param string $file
217
     *
218
     * @throws Exception
219
     *
220
     * @return mixed
221
     */
222 View Code Duplication
    public function rollbackMigration($file)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
223
    {
224
        $migration = $this->getMigrationObjectByFileName($file);
225
226
        $this->checkTransactionAndRun($migration, function () use ($migration, $file) {
227
            if ($migration->down() === false) {
228
                throw new Exception("<error>Can't rollback migration:</error> {$file}.php");
229
            }
230
        });
231
232
        $this->removeSuccessfulMigrationFromLog($file);
233
    }
234
235
    /**
236
     * Remove a migration name from the database so it can be run again.
237
     *
238
     * @param string $file
239
     *
240
     * @return void
241
     */
242
    public function removeSuccessfulMigrationFromLog($file)
243
    {
244
        $this->database->removeSuccessfulMigrationFromLog($file);
245
    }
246
247
    /**
248
     * Delete migration file.
249
     *
250
     * @param string $migration
251
     *
252
     * @return bool
253
     */
254
    public function deleteMigrationFile($migration)
255
    {
256
        return $this->files->delete($this->getMigrationFilePath($migration));
257
    }
258
259
    /**
260
     * Get array of migrations that should be ran.
261
     *
262
     * @return array
263
     */
264
    public function getMigrationsToRun()
265
    {
266
        $allMigrations = $this->getAllMigrations();
267
268
        $ranMigrations = $this->getRanMigrations();
269
270
        return array_diff($allMigrations, $ranMigrations);
271
    }
272
273
    /**
274
     * Move migration files.
275
     *
276
     * @param array $files
277
     * @param string $toDir
278
     *
279
     * @return int
280
     */
281
    public function moveMigrationFiles($files = [], $toDir = '')
282
    {
283
        $toDir = trim($toDir ?: $this->dir_archive, '/');
284
        $files = $files ?: $this->getAllMigrations();
285
        $this->files->createDirIfItDoesNotExist("$this->dir/$toDir");
286
287
        $count = 0;
288
        foreach ($files as $migration) {
289
            $from = $this->getMigrationFilePath($migration);
290
            $to = "$this->dir/$toDir/$migration.php";
291
292
            if ($from == $to) {
293
                continue;
294
            }
295
296
            $flag = $this->files->move($from, $to);
297
298
            if ($flag) {
299
                $count++;
300
            }
301
        }
302
303
        return $count;
304
    }
305
306
    /**
307
     * Construct migration file name from migration name and current time.
308
     *
309
     * @param string $name
310
     *
311
     * @return string
312
     */
313
    protected function constructFileName($name)
314
    {
315
        list($usec, $sec) = explode(' ', microtime());
316
317
        $usec = substr($usec, 2, 6);
318
319
        return date('Y_m_d_His', $sec).'_'.$usec.'_'.$name;
320
    }
321
322
    /**
323
     * Get a migration class name by a migration file name.
324
     *
325
     * @param string $file
326
     *
327
     * @return string
328
     */
329
    protected function getMigrationClassNameByFileName($file)
330
    {
331
        $fileExploded = explode('_', $file);
332
333
        $datePart = implode('_', array_slice($fileExploded, 0, 5));
334
        $namePart = implode('_', array_slice($fileExploded, 5));
335
336
        return Helpers::studly($namePart.'_'.$datePart);
337
    }
338
339
    /**
340
     * Replace all placeholders in the stub.
341
     *
342
     * @param string $template
343
     * @param array  $replace
344
     *
345
     * @return string
346
     */
347
    protected function replacePlaceholdersInTemplate($template, array $replace)
348
    {
349
        foreach ($replace as $placeholder => $value) {
350
            $template = str_replace("__{$placeholder}__", $value, $template);
351
        }
352
353
        return $template;
354
    }
355
356
    /**
357
     * Resolve a migration instance from a file.
358
     *
359
     * @param string $file
360
     *
361
     * @throws Exception
362
     *
363
     * @return MigrationInterface
364
     */
365
    protected function getMigrationObjectByFileName($file)
366
    {
367
        $class = $this->getMigrationClassNameByFileName($file);
368
369
        $this->requireMigrationFile($file);
370
371
        $object = new $class();
372
373
        if (!$object instanceof MigrationInterface) {
374
            throw new Exception("Migration class {$class} must implement Arrilot\\BitrixMigrations\\Interfaces\\MigrationInterface");
375
        }
376
377
        return $object;
378
    }
379
380
    /**
381
     * Require migration file.
382
     *
383
     * @param string $file
384
     *
385
     * @return void
386
     */
387
    protected function requireMigrationFile($file)
388
    {
389
        $this->files->requireFile($this->getMigrationFilePath($file));
390
    }
391
392
    /**
393
     * Get path to a migration file.
394
     *
395
     * @param string $migration
396
     *
397
     * @return string
398
     */
399
    protected function getMigrationFilePath($migration)
400
    {
401
        $files = Helpers::rGlob("$this->dir/$migration.php");
402
        if (count($files) != 1) {
403
            throw new \Exception("Not found migration file");
404
        }
405
406
        return $files[0];
407
    }
408
409
    /**
410
     * If package arrilot/bitrix-iblock-helper is loaded then we should disable its caching to avoid problems.
411
     */
412
    private function disableBitrixIblockHelperCache()
413
    {
414
        if (class_exists('\\Arrilot\\BitrixIblockHelper\\IblockId')) {
415
            IblockId::setCacheTime(0);
416
            if (method_exists('\\Arrilot\\BitrixIblockHelper\\IblockId', 'flushLocalCache')) {
417
                IblockId::flushLocalCache();
418
            }
419
        }
420
421
        if (class_exists('\\Arrilot\\BitrixIblockHelper\\HLBlock')) {
422
            HLBlock::setCacheTime(0);
423
            if (method_exists('\\Arrilot\\BitrixIblockHelper\\HLBlock', 'flushLocalCache')) {
424
                HLBlock::flushLocalCache();
425
            }
426
        }
427
    }
428
429
    /**
430
     * @param MigrationInterface $migration
431
     * @param callable $callback
432
     * @throws Exception
433
     */
434
    protected function checkTransactionAndRun($migration, $callback)
435
    {
436
        if ($migration->useTransaction($this->use_transaction)) {
437
            $this->database->startTransaction();
438
            Logger::log("Начало транзакции", Logger::COLOR_LIGHT_BLUE);
439
            try {
440
                $callback();
441
            } catch (\Exception $e) {
442
                $this->database->rollbackTransaction();
443
                Logger::log("Откат транзакции из-за ошибки '{$e->getMessage()}'", Logger::COLOR_LIGHT_RED);
444
                throw $e;
445
            }
446
            $this->database->commitTransaction();
447
            Logger::log("Конец транзакции", Logger::COLOR_LIGHT_BLUE);
448
        } else {
449
            $callback();
450
        }
451
    }
452
}
453